package com.ruoyi.app.business.login;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.MD5;
import com.ruoyi.app.company.CompanyBusiness;
import com.ruoyi.app.converter.UserConverter;
import com.ruoyi.common.core.constant.HttpStatus;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.exception.UserAuthException;
import com.ruoyi.common.core.utils.CommonUtils;
import com.ruoyi.common.core.utils.JwtUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.redis.service.RedisService;
import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.service.company.entity.CompanyEntity;
import com.ruoyi.service.company.entity.UserCompanyEntity;
import com.ruoyi.service.company.enums.UserRoleEnum;
import com.ruoyi.service.user.entity.UserEntity;
import com.ruoyi.service.user.enums.UserTypeEnum;
import com.ruoyi.service.user.model.UserInfoModel;
import com.ruoyi.service.user.service.UserService;
import io.jsonwebtoken.Claims;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

/**
 * <p>
 * 作用：Token 计算处理服务器
 * </p>
 *
 * @author Zane
 * @since 2025-09-29 4:45
 */
@Component
@RefreshScope
public class TokenBusiness {

  @Resource
  private RedisService redisService;
  @Resource
  private UserService userService;
  @Resource
  private CompanyBusiness companyBusiness;
  /**
   * 缓存有效期，默认120分钟，单位分钟
   */
  @Value("${kp.token.expire:120}")
  private Integer expire_minute;
  /**
   * 缓存有效期，默认7天，单位秒
   */
  @Value("${kp.token.user.expire:7}")
  private Long expire_day;

  /**
   * 数据转换器
   */
  final UserConverter userConverter = UserConverter.INSTANCE;


  /**
   * 创建令牌
   */
  public String createToken(final UserEntity userEntity) {
    // Jwt存储信息
    final Map<String, Object> claimsMap = buildClaims(userEntity);
    refreshUser(userEntity);
    return JwtUtils.createToken(claimsMap);
  }

  public void checkToken(HttpServletRequest request) {
    // 如果前端设置了令牌前缀，则裁剪掉前缀
    final String token = SecurityUtils.getToken(request);
    if (StrUtil.isBlank(token)) {
      throw new UserAuthException(HttpStatus.UNAUTHORIZED, "Header中未获得Token信息");
    }
    final Claims claims = JwtUtils.parseToken(token);
    if (claims == null) {
      throw new UserAuthException(HttpStatus.UNAUTHORIZED, "Token解码失败");
    }
    //验证Token续期限制期限,如果已超期.则重登陆
    final long currentTimesMillis = System.currentTimeMillis();
    if (JwtUtils.getLongValue(claims, SecurityConstants.ALLOW_TOKEN_REFRESH_TIME)
        < currentTimesMillis) {
      throw new UserAuthException(HttpStatus.UNAUTHORIZED, "长时间未操作,需要重新登录");
    }
    //验证Token期限,已超期,需要refreshToken
    if (JwtUtils.getLongValue(claims, SecurityConstants.ALLOW_TOKEN_ACCESS_TIME)
        < currentTimesMillis) {
      throw new UserAuthException(HttpStatus.FORBIDDEN, "授权过期,重新换取Token");
    }
  }

  /**
   * 获取用户身份信息
   *
   * @return 用户信息
   */
  public UserInfoModel getLoginUser(HttpServletRequest request) {
    // 获取请求携带的令牌
    return getLoginUser(SecurityUtils.getToken(request));
  }

  /**
   * 获取用户身份信息
   *
   * @return 用户信息
   */
  public UserInfoModel getLoginUser(String token) {
    if (StringUtils.isNotEmpty(token)) {
      final Claims claims = JwtUtils.parseToken(token);
      final String userCacheKey = JwtUtils.getUserKey(claims);
      final UserInfoModel user = redisService.getCacheObject(userCacheKey);
      if (Objects.isNull(user)) {
        //重新缓存
        final String userId = JwtUtils.getUserId(claims);
        return refreshUser(userService.getById(userId));
      }
      return user;
    }
    throw new UserAuthException(HttpStatus.UNAUTHORIZED, "用户登录失效");
  }

  /**
   * 删除用户缓存信息
   */
  public void delLoginUser(String token) {
//    String userkey = JwtUtils.getUserKey(token);
//    redisService.deleteObject(getTokenKey(userkey));
  }


  /**
   * 刷新用户信息
   */
  public void refreshUser(final UserInfoModel userInfo) {
    final String userCacheKey = userCacheKey(userInfo);
    //可以优化随机一下有效期避免扎堆失效
    redisService.setCacheObject(userCacheKey, userInfo, expire_day, TimeUnit.DAYS);
  }

  /**
   * 刷新用户信息
   */
  public void refreshUser(final Long userId) {
    final UserEntity userEntity = userService.getById(userId);
    refreshUser(userEntity);
  }

  /**
   * 刷新用户信息
   */
  public UserInfoModel refreshUser(final UserEntity userEntity) {
    //组装缓存信息
    final UserInfoModel userInfo = userConverter.toUserModel(userEntity);
    if (UserTypeEnum.DEMANDER_USER.eq(userInfo.getUserType())) {
      //封装注册公司信息
//      final CompanyEntity company = companyService.getByManager(userInfo.getUserId());
//      if (Objects.nonNull(company)) {
//        userInfo.setCompanyId(company.getComId()).setCompanyStatus(company.getStatus());
//      }
      final UserCompanyEntity userCompany = companyBusiness.getUserCompanyByUserId(
          userInfo.getUserId());
      if (Objects.nonNull(userCompany)) {
        final CompanyEntity companyInfo = CommonUtils.requireNotNull(
            companyBusiness.getById(userCompany.getComId()), "数据有误,请联系管理员");
        userInfo.setCompanyId(companyInfo.getComId())
            .setCompanyStatus(companyInfo.getStatus())
            .setCompanyName(companyInfo.getComName())
            .setCompanyManager(UserRoleEnum.ADMIN.eqCode(userCompany.getUserRole()));
      }
    }
    //根据信息构建用户缓存扩表
    refreshUser(userInfo);
    return userInfo;
  }

  /**
   * 续签令牌
   */
  public String refreshToken(String token) {
    //判断客户端允许续签时间范围: 1 token已经过期,并且在允许续签时间之前
    //前端规则 401 需要重新登录 ,  403 刷新token
    final Claims claims = JwtUtils.parseToken(token);
    if (claims == null) {
      throw new UserAuthException(HttpStatus.UNAUTHORIZED, "Token解码失败");
    }
    //验证Token续期限制期限,如果已超期.则重登陆
    final long currentTimesMillis = System.currentTimeMillis();
    if (JwtUtils.getLongValue(claims, SecurityConstants.ALLOW_TOKEN_REFRESH_TIME)
        < currentTimesMillis) {
      throw new UserAuthException(HttpStatus.UNAUTHORIZED, "长时间未操作,需要重新登录");
    }
    final UserInfoModel loginUser = getLoginUser(token);
    final Map<String, Object> claimsMap = buildClaims(loginUser);
    redisService.expire(userCacheKey(loginUser), expire_day, TimeUnit.DAYS);
    return JwtUtils.createToken(claimsMap);
  }

  public String userCacheKey(UserEntity userEntity) {
    return userCacheKey(userEntity.getUserId(), userEntity.getUserName(), userEntity.getUserType());
  }

  public String userCacheKey(UserInfoModel userInfo) {
    return userCacheKey(userInfo.getUserId(), userInfo.getUserName(), userInfo.getUserType());
  }

  public String userCacheKey(Long userId, String username, String userType) {
    return StrUtil.format("kp:userInfo:{}",
        MD5.create().digestHex(StrUtil.format("{}&{}&{}", userId, username, userType)));
  }

  private Map<String, Object> buildClaims(UserEntity userEntity) {
    return buildClaims(userEntity.getUserId(), userEntity.getUserName(), userEntity.getUserType());
  }

  private Map<String, Object> buildClaims(UserInfoModel userInfoModel) {
    return buildClaims(userInfoModel.getUserId(), userInfoModel.getUserName(),
        userInfoModel.getUserType());
  }

  private Map<String, Object> buildClaims(Long userId, String username, String userType) {
    // Jwt存储信息
    Map<String, Object> claimsMap = new HashMap<String, Object>();
    claimsMap.put(SecurityConstants.USER_KEY, userCacheKey(userId, username, userType));
    claimsMap.put(SecurityConstants.DETAILS_USER_ID, userId);
    claimsMap.put(SecurityConstants.DETAILS_USERNAME, username);
    claimsMap.put(SecurityConstants.DETAILS_USERTYPE, userType);
    //当前时间戳 +expire_minute 分钟
    final long currentTimeMillis = System.currentTimeMillis();
    final long expire_time = currentTimeMillis + expire_minute * 60 * 1000L;
    claimsMap.put(SecurityConstants.ALLOW_TOKEN_ACCESS_TIME, expire_time);
    final long expire_time_limit = currentTimeMillis + expire_minute * 2 * 60 * 1000L;
    claimsMap.put(SecurityConstants.ALLOW_TOKEN_REFRESH_TIME, expire_time_limit);
    return claimsMap;
  }
}
